package cn.melonlib.cores.graphql.comp.service;

import cn.melonlib.cores.commons.comp.utils.EntityWrapperContainer;
import cn.melonlib.cores.graphql.comp.config.ScalarTypeConfig;
import cn.melonlib.cores.graphql.comp.config.WorkerParametersReader;
import cn.melonlib.cores.graphql.config.bean.GraphQLMutationWorker;
import cn.melonlib.cores.graphql.config.bean.GraphQLQueryWorker;
import cn.melonlib.cores.graphql.model.vo.GraphQLMutationVo;
import cn.melonlib.cores.graphql.model.vo.GraphQLParameterVo;
import cn.melonlib.cores.graphql.model.vo.GraphQLQueryVo;
import cn.melonlib.cores.graphql.model.vo.GraphQLServiceVo;
import cn.melonlib.cores.jpa.model.bean.collections.JsonMap;
import cn.melonlib.cores.model.anno.WorkerComment;
import cn.melonlib.cores.model.anno.WorkerQuery;
import cn.melonlib.cores.model.anno.WorkerService;
import cn.melonlib.cores.model.anno.WorkerStore;
import cn.melonlib.graphql.cores.comp.engine.GraphqlEngine;
import cn.melonlib.graphql.cores.comp.utils.GraphqlScanner;
import com.fasterxml.jackson.databind.ObjectMapper;
import graphql.GraphQL;
import graphql.schema.*;
import graphql.schema.idl.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static graphql.schema.GraphQLObjectType.newObject;

@Data
@EqualsAndHashCode(callSuper = false)
@Service
public class GraphQLService extends ApplicationObjectSupport {

    @Resource
    private GraphqlScanner graphqlScanner;

    @Resource
    private GraphqlEngine graphqlEngine;

    private ConcurrentMap<String, TypeRuntimeWiring.Builder> builderConcurrentMap = new ConcurrentHashMap<>();

    @Value("${graph_ql.provider:graphql/**/*.graphqls}")
    private List<String> providerQlPaths;

    @Value("${graph_ql.public}")
    private List<String> publicQlPaths;

    /**
     * 私有的接口，需要登录
     */
    private GraphQL ql;

    /**
     * 公共的接口，无需登录
     */
    private GraphQL publicQl;




    @javax.annotation.Resource
    private EntityWrapperContainer entityWrapperContainer;

    @javax.annotation.Resource
    private ObjectMapper jsonMapper;

    private JsonMap<GraphQLServiceVo> serviceMap=new JsonMap<>();



    @Resource
    private WorkerParametersReader workerParametersReader;

    @Resource
    private ScalarTypeConfig scalarTypeConfig;

    public void init() throws Exception {

        try {
            logger.info("初始化GraphQl Schemas...");
            Map<String, Object> beans = this.getApplicationContext().getBeansWithAnnotation(WorkerService.class);

            beans.entrySet().stream().forEach(entry -> {
                Class cls = AopUtils.getTargetClass(entry.getValue());
                WorkerService serviceConfig = AnnotationUtils.findAnnotation(cls, WorkerService.class);
                String serviceName = StringUtils.defaultIfBlank(serviceConfig.name(), entry.getKey());
                GraphQLServiceVo graphQLServiceVo = new GraphQLServiceVo();
                graphQLServiceVo.setName(serviceName);
                if (ObjectUtils.isNotEmpty(AnnotationUtils.findAnnotation(cls, WorkerComment.class))) {
                    graphQLServiceVo.setNotes(AnnotationUtils.findAnnotation(cls, WorkerComment.class).value());
                }
                graphQLServiceVo.setMutations(new ArrayList<>());
                graphQLServiceVo.setQueries(new ArrayList<>());
                Method[] methods = cls.getMethods();
                logger.info("开始注册GQL Service Query-----------" + serviceName);
                List<String> ignores = Arrays.stream(serviceConfig.ignores()).collect(Collectors.toList());
                TypeRuntimeWiring.Builder queryBuilder = TypeRuntimeWiring.newTypeWiring("Query");
                Arrays.stream(methods).filter(m -> AnnotationUtils.findAnnotation(m, WorkerQuery.class) != null)
                        .filter(m -> !ignores.contains(m.getName()))
                        .forEach(m -> {
                            WorkerQuery queryAnno = AnnotationUtils.findAnnotation(m, WorkerQuery.class);
                            String methodName = StringUtils.defaultIfBlank(queryAnno.name(), m.getName());
                            String queryName = serviceName + "_" + methodName;
                            queryBuilder.dataFetcher(queryName, env -> {
                                        GraphQLQueryWorker worker = (GraphQLQueryWorker)this.getApplicationContext().getBean(queryAnno.converter());
                                        return worker.query(env, m, entry.getValue());
                                    }
                            );
                            GraphQLQueryVo graphQLQueryVo = new GraphQLQueryVo();
                            String api=queryName + "(" + Arrays.stream(m.getParameters())
                                    .map(parameter -> new StringBuilder(parameter.getName()).append(":")
                                            .append(parameter.getType().getSimpleName())).collect(Collectors.joining(",")) + "):" + m.getReturnType().getSimpleName();
                            graphQLQueryVo.setApi(api);
                            graphQLQueryVo.setName(methodName);
                            graphQLQueryVo.setNotes(AnnotationUtils.findAnnotation(m,WorkerComment.class).value());
                            graphQLQueryVo.setType(m.getReturnType().getName());
                            graphQLQueryVo.setParameters(Arrays.stream(m.getParameters()).map(parameter -> {
                                GraphQLParameterVo graphQLParameterVo=new GraphQLParameterVo();
                                graphQLParameterVo.setName(parameter.getName());
                                graphQLParameterVo.setType(parameter.getType().getName());
                                return graphQLParameterVo;
                            }).collect(Collectors.toList()));
                            graphQLServiceVo.getQueries().add(graphQLQueryVo);
                            logger.info("注册Query:" + api);
                        });
                this.addBuilder(serviceName + "-Query", queryBuilder);
                logger.info("开始注册GQL Service Mutation-----------" + serviceName);
                TypeRuntimeWiring.Builder mutationBuilder = TypeRuntimeWiring.newTypeWiring("Mutation");
                Arrays.stream(methods).filter(m -> AnnotationUtils.findAnnotation(m, WorkerStore.class) != null)
                        .filter(m -> !ignores.contains(m.getName()))
                        .forEach(m -> {
                            WorkerStore mutationAnno = AnnotationUtils.findAnnotation(m, WorkerStore.class);
                            String methodName = StringUtils.defaultIfBlank(mutationAnno.name(), m.getName());
                            String queryName = serviceName + "_" + methodName;
                            mutationBuilder.dataFetcher(queryName, env -> {
                                        GraphQLMutationWorker worker = (GraphQLMutationWorker)this.getApplicationContext().getBean(mutationAnno.converter());
                                        return worker.mutation(env, m, entry.getValue());
                                    }
                            );
                            String api=queryName + "(" + Arrays.stream(m.getParameters())
                                    .map(parameter -> new StringBuilder(parameter.getName()).append(":")
                                            .append(parameter.getType().getSimpleName())).collect(Collectors.joining(",")) + "):" + m.getReturnType().getSimpleName();
                            logger.info(api);
                            GraphQLMutationVo graphQLMutationVo=new GraphQLMutationVo();
                            graphQLMutationVo.setApi(api);
                            graphQLMutationVo.setName(methodName);
                            graphQLMutationVo.setNotes(AnnotationUtils.findAnnotation(m,WorkerComment.class).value());
                            graphQLMutationVo.setType(m.getReturnType().getName());
                            graphQLMutationVo.setParameters(Arrays.stream(m.getParameters()).map(parameter -> {
                                GraphQLParameterVo graphQLParameterVo=new GraphQLParameterVo();
                                graphQLParameterVo.setName(parameter.getName());
                                graphQLParameterVo.setType(parameter.getType().getName());
                                return graphQLParameterVo;
                            }).collect(Collectors.toList()));
                            logger.info("注册Mutation:" + api);
                            graphQLServiceVo.getMutations().add(graphQLMutationVo);
                        });
                this.addBuilder(serviceName + "-Mutation", mutationBuilder);
//                Arrays.stream(methods).filter(m -> AnnotationUtils.findAnnotation(m, GraphQLMutation.class) != null).forEach(logger::info);
                serviceMap.append(serviceName,graphQLServiceVo);
            });
            buildGraphql();
            logger.info("初始化GraphQl Schemas 完成！！！");
        } catch (Exception e) {
            this.logger.info(e.getMessage(), e);
        }

    }

    public void buildGraphql() throws Exception{
        SchemaGenerator schemaGenerator = new SchemaGenerator();
        TypeDefinitionRegistry registry = null;
        String [] providerPaths=providerQlPaths.stream().filter(StringUtils::isNotBlank).collect(Collectors.toList()).toArray(new String[]{});
        if(ArrayUtils.isNotEmpty(providerPaths)) {
            try {
                registry = typeDefinitionRegistry(providerPaths);
            } catch (IOException e) {
                e.printStackTrace();
            }
            //Schema文件SDL编写，实现增删改查
            GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(registry, runtimeWiring());

            //通过配置实现数据库表自动生成增删改查
            graphQLSchema = getGraphQLSchema(graphQLSchema);

            //获取私有接口，需要登录
            this.setQl(GraphQL.newGraphQL(graphQLSchema).build());
        }
        String [] publicPaths=publicQlPaths.stream().filter(StringUtils::isNotBlank).collect(Collectors.toList()).toArray(new String[]{});
        if(ArrayUtils.isNotEmpty(publicPaths)) {
            this.setPublicQl(GraphQL.newGraphQL(getGraphQLSchema(schemaGenerator
                    .makeExecutableSchema(typeDefinitionRegistry(publicPaths), runtimeWiring()))).build());
        }
    }

    //schema 类型注册 主要是用来整合各个schema文件路径
    public TypeDefinitionRegistry typeDefinitionRegistry(String ... paths) throws IOException {
        SchemaParser schemaParser = new SchemaParser();
        //获取所有的graphqls文件进行加载

        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
//        Resource[] resources = resolver.getResources("classpath:graphql/**/*.graphqls");
        return Arrays.stream(paths).flatMap(path->{
            try {
                return Arrays.stream(resolver.getResources(path));
            }catch (Exception e){
                return Stream.empty();
            }
        }).reduce(new TypeDefinitionRegistry(),(tr,resource)->{
            try (Reader in = new BufferedReader(new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))) {
                tr.merge(schemaParser.parse(in));
            }catch (Exception e){
                logger.error(e.getMessage(),e);
            }
            return tr;
        },(tr,resource)->null);
    }


    public RuntimeWiring runtimeWiring() {
        RuntimeWiring.Builder builder = RuntimeWiring.newRuntimeWiring();
        scalarTypeConfig.allScalarTypes().forEach(builder::scalar);
//        builder.scalar(dateType)
//                .scalar(viewEntityType).scalar(fileType)
//                .scalar(filterType).scalar(inputType)
//                .scalar(byteDataType).scalar(listType);
        if (this.builderConcurrentMap != null && this.builderConcurrentMap.size() > 0) {
            for (Map.Entry<String, TypeRuntimeWiring.Builder> entry : this.builderConcurrentMap.entrySet()) {
                builder.type(entry.getValue());
            }
        }
        return builder.build();
    }

    public GraphQLSchema getGraphQLSchema(GraphQLSchema graphQLSchema) {
        List<GraphQLFieldDefinition> query_list = new ArrayList<>();
        List<GraphQLFieldDefinition> mutation_list = new ArrayList<>();
        if ((query_list != null && query_list.size() > 0) || (mutation_list != null && mutation_list.size() > 0)) {
            GraphQLObjectType.Builder newQueryType = newObject().name("Query")
                    .fields(graphQLSchema.getQueryType().getFieldDefinitions())
                    .fields(query_list);

            GraphQLObjectType.Builder newMutaionType = newObject().name("Mutation")
                    .fields(graphQLSchema.getMutationType().getFieldDefinitions())
                    .fields(mutation_list);


            GraphQLSchema.Builder schema = GraphQLSchema.newSchema()
                    .query(newQueryType).mutation(newMutaionType);

            return schema.build();
        }

        return graphQLSchema;
    }

    public void addBuilder(String name, TypeRuntimeWiring.Builder builder) {
        builderConcurrentMap.put(name, builder);
    }

    public void addQuery(String name, JsonMap<DataFetcher> dataFetchers) {
        TypeRuntimeWiring.Builder builder = TypeRuntimeWiring.newTypeWiring("Query");
        dataFetchers.keySet().stream().forEach(key -> {
            builder.dataFetcher(key, dataFetchers.get(key));
        });
        this.addBuilder(name + "-Query", builder);
    }

    public void addMutation(String name, JsonMap<DataFetcher> dataFetchers) {
        TypeRuntimeWiring.Builder builder = TypeRuntimeWiring.newTypeWiring("Mutation");
        dataFetchers.keySet().stream().forEach(key -> {
            builder.dataFetcher(key, dataFetchers.get(key));
        });
        this.addBuilder(name + "-Mutation", builder);
    }


}
